#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <omp.h>

#define TOTAL_CHARS 1000
#define TOTAL_WORDS 100
#define FILE_NAME "shakespe.txt"
#define MIN_THREADS 1

int palindrome_word(char *); // verifica se palavra é palindromo
int palindrome_phrase(char [][25], int); // verifica se frase é palindromo
void clean_tokens(char [][25], int *); // limpa vetor de tokens
int bline(char **, char *); // Retorna 1 linha do bloco
int fsize(char *); // Retorna o tamanho do arquivo lido

int main (int argc, char *argv[]) {

  /*
    totalw - total de palavras palindromes
    totalp - total de frases palindromes
    auxtotalw - auxiliar (parte paralela) para coletar palavras palindromes de um bloco
    auxtotalp - auxiliar (parte paralela) para coletar frases palindromes de um bloco
    nthreads - número de threads
    fname - nome do arquivo
  */
  int totalw = 0, totalp = 0, auxtotalw = 0, auxtotalp = 0, bsize, nthreads;
  char fname[30];

  strcpy(fname, (argc > 1 && argv[1] != NULL) ? argv[1] : FILE_NAME); // Seta o nome do arquivo dependendo dos argumentos
  nthreads = ((argc > 2 && argv[2] != NULL) ? atoi(argv[2]) : MIN_THREADS); // Seta o número de threads dependendo dos argumentos

  // Arquivo é dividido em blocos de texto, e cada thread verifica nesse bloco a ocorrência dos palindromos

  bsize = fsize(fname)/nthreads; // Tamando do bloco em bytes

  omp_set_num_threads(nthreads); // Seta número de threads a serem usadas

  // Parte paralela: divide processamento e threads, cada thread sendo responsável por um bloco de texto
  #pragma omp parallel firstprivate(bsize, auxtotalw, auxtotalp) reduction (+: totalw, totalp)
  {
    /*
      tokens - array de tokens, que guardaram as palavras de uma frase
      line - string com a linha do bloco (é incrementada a cada loop)
      ntokens - número de tokens correntes
      block - guardará o bloco de texto daquela thread
    */
    char tokens[TOTAL_WORDS][25], line[TOTAL_CHARS];
    int ntokens = 0, i;

    FILE *f;
    char *block = (char*) calloc(bsize+1, sizeof(char));

    // Lê o bloco de texto dependendo do número da thread (omp_get_thread_num) que está no arquivo
    if (!(f = fopen(fname, "r")) || fseek(f, bsize*omp_get_thread_num(), SEEK_SET) ||
        !fread(block, sizeof(char), bsize, f))
      exit(1);

    bline(&block, line); // Recupera a primeira linha do bloco

    do {

      char *token, *rest, *ptr = line;

      // Separa a linha do bloco em tokens (palavras) utilizando a função strtok_r
      while((token = strtok_r(ptr, " ,.-()", &rest))) {
        strcpy(tokens[ntokens], token);
        ntokens++;
        ptr = rest;
      }


      // Percorre o array de tokens verificando se é palindromo ou não
      for(i=0 ; i<ntokens ; i++)
        if(palindrome_word(tokens[i]))
          auxtotalw++;

      // Verifica se o array de tokens é palindromo
      if(palindrome_phrase(tokens, ntokens))
        auxtotalp++;

      // Limpa o array tokens para a proxima iteração
      clean_tokens(tokens, &ntokens);

    } while(bline(&block, line)); // Percorre todas as linhas do bloco até acabar

    fclose(f);

    #pragma omp barrier // Espera todas as threads terminarem
    #pragma omp critical // Garante que a última thread terminou totalmente

    totalw += auxtotalw; // Soma ao total o número de palavras palindromes de cada thread
    totalp += auxtotalp; // Soma ao total o número de frases palindromes de cada thread
  }

  printf("Total de palavras palindromes: %d\n", totalw);
  printf("Total de frases palindromes: %d\n", totalp);

  return 0;
}

int palindrome_word(char *str) {

  int size, i, j, palindrome = 1;

  size = strlen(str);

  if(size > 1)
    for(i=0, j=size-1; i < size ; i++, j--)
      palindrome = palindrome && (str[i] == str[j]);
  else
    palindrome = 0;

  return palindrome;

}

int palindrome_phrase(char tokens[][25], int n_tokens) {

  int i, j, palindrome = 1;

  if (n_tokens > 1)
    for(i=0, j=n_tokens-1; i < n_tokens ; i++, j--)
      palindrome = palindrome && (strcmp(tokens[i], tokens[j]) == 0);
  else
    palindrome = 0;

  return palindrome;

}

void clean_tokens(char tokens[][25], int *n_tokens) {

  int i;

  for(i=0; i<40 ;i++)
    strcpy(tokens[i], "");

  *n_tokens = 0;

}

int bline(char **block, char *str) {

  char c, aux[TOTAL_CHARS] = "";
  int i = 0;

  c = **block, (*block)++; // Caminha com o primeiro caracter do bloco

  if(c != '\0') {
    while((int)c != 13 && c != '\n' && c != '\0') { // Verifica se é quebra de linha ou fim de linha (^M, \n, \0)
      if((int)c != 9)
        aux[i] = c, i++;
      else
        aux[i] = ' ', i++;
      c = **block, (*block)++; // Caminha para o próximo caracter do bloco
    }

    strcpy(str, aux);

    return 1;
  } else
    return 0;

}

int fsize(char *fname) {

  int size;
  FILE *f;

  // Caminha com o ponteiro até o final do arquivo, e verifica o número de bytes, que corresponde ao tamanho total
  if(!(f = fopen(fname, "r")) || fseek(f, 0, SEEK_END) || !(size = ftell(f)))
    exit(1);

  fclose(f);

  return size;
}
